package com.muyan.common.utils;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.GZIPInputStream;

import static com.muyan.common.constant.Const.UTF_8;

public class HttpUtil {

    protected static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36";

    /****GET*****/
    public static String doGet(String url, Config config) throws Exception {
        HttpURLConnection httpConn = getUrlConnction(url, config);
        httpConn.connect();
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), config.charset))) {//StandardCharsets.UTF_8
            String line;
            int i = 0;
            while ((line = br.readLine()) != null) {
                if (i++ > 0) sb.append("\n");
                sb.append(line);
            }
        } catch (Exception e) {
            System.err.println("opt=doGet exception,msg=" + e.getMessage());
        }
        return sb.toString();
    }

    public static String doGet(String url) throws Exception {
        return doGet(url, new Config());
    }

    /****POST*****/
    public static String doPost(String url, Config config) throws Exception {
        HttpURLConnection httpConn = getUrlConnction(url, config);

        httpConn.setDoInput(true);
        httpConn.setDoOutput(true);
        httpConn.connect();
        StringBuilder sb = new StringBuilder();
        try {
            String finalData = getPostData(config.data, config.encodeParams);
            if (finalData != null) {
                try (BufferedOutputStream out = new BufferedOutputStream(httpConn.getOutputStream())) {
                    out.write(finalData.getBytes(config.charset));
                    out.flush();
                }
            }
            if (config.gzip) {
                try (
                        GZIPInputStream gzipin = new GZIPInputStream(httpConn.getInputStream());
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ) {
                    byte[] buffer = new byte[4096];
                    int n;
                    while ((n = gzipin.read(buffer)) >= 0) {
                        baos.write(buffer, 0, n);
                    }
                    baos.flush();
                    return baos.toString(UTF_8);
                }
            } else {
                try (BufferedReader br = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), config.charset))) {
                    String line;
                    int i = 0;
                    while ((line = br.readLine()) != null) {
                        if (i++ > 0) sb.append("\n");
                        sb.append(line);
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("opt=doPost exception,msg=" + e.getMessage());
        }
        return sb.toString();
    }

    public static String doPost(String url) throws Exception {
        return doPost(url, new Config());
    }

    protected static HttpURLConnection getUrlConnction(String url, Config config) throws Exception {
        URL reqURL = new URL(url);
        HttpURLConnection httpConn = null;
        HttpsURLConnection httpsConn = null;
        if (config == null) config = new Config();
        if (config.proxyor.useProxy && config.https) {
            SocketAddress addr = new InetSocketAddress(config.proxyor.proxyIp, config.proxyor.proxyPort);
            httpsConn = (HttpsURLConnection) reqURL.openConnection(new Proxy(Proxy.Type.HTTP, addr));
        } else if (config.proxyor.useProxy) {
            SocketAddress addr = new InetSocketAddress(config.proxyor.proxyIp, config.proxyor.proxyPort);
            httpConn = (HttpURLConnection) reqURL.openConnection(new Proxy(Proxy.Type.HTTP, addr));
        } else if (config.https) {
            httpsConn = (HttpsURLConnection) reqURL.openConnection();
        } else {
            httpConn = (HttpURLConnection) reqURL.openConnection();
        }

        if (config.https) {
            TrustManager[] tm = {new MyX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tm, new java.security.SecureRandom());
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            httpsConn.setSSLSocketFactory(ssf);
            httpConn = httpsConn;
        }

        //common property
        httpConn.setConnectTimeout(config.connTimeout);
        httpConn.setReadTimeout(config.readTimeout);
        httpConn.setRequestProperty("accept", "*/*");
        httpConn.setRequestProperty("connection", "Keep-Alive");
        httpConn.setRequestProperty("user-agent", USER_AGENT);
        if (config.headers != null) {
            for (Object h : config.headers.keySet()) {
                httpConn.setRequestProperty(h.toString(), config.headers.get(h).toString());
            }
        }
        return httpConn;
    }

    @SuppressWarnings("unchecked")
    protected static String getPostData(Object data, boolean encode) throws UnsupportedEncodingException {
        String finalData = null;
        if (data != null) {
            if (data instanceof Map) {
                Map mapData = (Map) data;
                StringBuilder params = new StringBuilder();
                Set<Entry> entrys = mapData.entrySet();
                boolean first = true;
                for (Entry entry : entrys) {
                    if (!first)
                        params.append("&");
                    else
                        first = false;

                    params.append(encode ? URLEncoder.encode(entry.getKey().toString(), UTF_8) : entry.getKey());
                    params.append('=');
                    params.append(entry.getValue() == null ? "" : (encode ? URLEncoder.encode(entry.getValue().toString(), UTF_8) : entry.getValue()));
                }
                finalData = params.toString();
            } else {
                finalData = data.toString();
            }
        }
        return finalData;
    }

    public static class Config {
        private int connTimeout = 30_000;
        private int readTimeout = 10_000;
        private Charset charset = StandardCharsets.UTF_8;
        private Object data;
        private Map headers;
        private boolean https;
        private boolean gzip;
        private boolean encodeParams = true;
        private Proxyor proxyor = new Proxyor(false);

        public Config() {
        }

        public Config(Object data) {
            this.data = data;
        }

        public Config(Object data, Map headers) {
            this.data = data;
            this.headers = headers;
        }

        public Object getData() {
            return data;
        }

        public Config setData(Object data) {
            this.data = data;
            return this;
        }

        public Map getHeaders() {
            return headers;
        }

        public Config setHeaders(Map headers) {
            this.headers = headers;
            return this;
        }

        public boolean isHttps() {
            return https;
        }

        public Config setHttps(boolean https) {
            this.https = https;
            return this;
        }

        public boolean isGzip() {
            return gzip;
        }

        public Config setGzip(boolean gzip) {
            this.gzip = gzip;
            return this;
        }

        public Proxyor getProxyor() {
            return proxyor;
        }

        public Config setProxyor(Proxyor proxyor) {
            this.proxyor = proxyor;
            return this;
        }

        public int getConnTimeout() {
            return connTimeout;
        }

        public Config setConnTimeout(int connTimeout) {
            this.connTimeout = connTimeout;
            return this;
        }

        public int getReadTimeout() {
            return readTimeout;
        }

        public Config setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public boolean isEncodeParams() {
            return encodeParams;
        }

        public Config setEncodeParams(boolean encodeParams) {
            this.encodeParams = encodeParams;
            return this;
        }

        public Charset getCharset() {
            return charset;
        }

        public Config setCharset(String charset) {
            this.charset = Charset.forName(charset);
            return this;
        }

        public Config setCharset(Charset charset) {
            this.charset = charset;
            return this;
        }
    }

    public static class Proxyor {
        private boolean useProxy;
        private String proxyIp;
        private Integer proxyPort;

        public Proxyor(boolean useProxy) {
            this.useProxy = useProxy;
        }

        public Proxyor(String proxyIp, Integer proxyPort) {
            this.useProxy = true;
            this.proxyIp = proxyIp;
            this.proxyPort = proxyPort;
        }

        public boolean isUseProxy() {
            return useProxy;
        }

        public void setUseProxy(boolean useProxy) {
            this.useProxy = useProxy;
        }

        public String getProxyIp() {
            return proxyIp;
        }

        public void setProxyIp(String proxyIp) {
            this.proxyIp = proxyIp;
        }

        public Integer getProxyPort() {
            return proxyPort;
        }

        public void setProxyPort(Integer proxyPort) {
            this.proxyPort = proxyPort;
        }
    }

    protected static class MyX509TrustManager implements X509TrustManager {

        public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }


}
